package priv.conceit.sc.hoxton.auth.authority.service.impl;

import cn.hutool.core.collection.CollUtil;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import priv.conceit.sc.hoxton.auth.authority.dao.UserMapper;
import priv.conceit.sc.hoxton.auth.authority.service.*;
import priv.conceit.sc.hoxton.auth.contractor.service.ContractorService;
import priv.conceit.sc.hoxton.auth.core.AuthProperties;
import priv.conceit.sc.hoxton.auth.core.chain.UserLoginAccountVerifyChain;
import priv.conceit.sc.hoxton.auth.org.service.CompanyService;
import priv.conceit.sc.hoxton.auth.org.service.DeptService;
import priv.conceit.sc.hoxton.common.cache.util.RedisExecuteUtil;
import priv.conceit.sc.hoxton.common.constant.auth.user.UserLoginDeviceTypeEnum;
import priv.conceit.sc.hoxton.common.constant.auth.user.UserTypeEnum;
import priv.conceit.sc.hoxton.common.constant.cache.CachePrefixKeyEnum;
import priv.conceit.sc.hoxton.common.entity.auth.authority.dto.*;
import priv.conceit.sc.hoxton.common.entity.auth.authority.pojo.*;
import priv.conceit.sc.hoxton.common.entity.auth.authority.qo.UserLoginQO;
import priv.conceit.sc.hoxton.common.entity.auth.contractor.pojo.Contractor;
import priv.conceit.sc.hoxton.common.entity.auth.org.pojo.Company;
import priv.conceit.sc.hoxton.common.entity.auth.org.pojo.Dept;
import priv.conceit.sc.hoxton.common.web.exception.WebException;
import priv.conceit.sc.hoxton.common.web.exception.WebExceptionEnum;
import priv.conceit.sc.hoxton.common.web.security.TokenUtil;

import java.util.*;
import java.util.stream.Collectors;

/**
 * <p>
 * 用户 服务实现类
 * </p>
 *
 * @author conceit
 * @since 2020-11-20
 */
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {

	@Autowired
	private UserLoginAccountVerifyChain chain;
	@Autowired
	private TokenUtil tokenUtil;
	@Autowired
	private RedisTemplate redisTemplate;
	@Autowired
	private AuthProperties authProperties;
	@Autowired
	private RoleService roleService;
	@Autowired
	private UserRoleService userRoleService;
	@Autowired
	private ResourceService resourceService;
	@Autowired
	private RoleResourceService roleResourceService;
	@Autowired
	private RedisExecuteUtil redisExecuteUtil;
	@Autowired
	private DeptService deptService;
	@Autowired
	private CompanyService companyService;
	@Autowired
	private ContractorService contractorService;

	/**
	 * login 用户统一登陆地址
	 *
	 * @param userLoginQO 用户登陆请求对象
	 * @return CurrentUserInfoDTO 响应
	 * @author conceit
	 * @date 2020/11/3 16:22
	 */
	@Override
	public CurrentUserInfoDTO login(UserLoginQO userLoginQO) {
		CurrentUserInfoDTO vo = new CurrentUserInfoDTO();
		//调用账号类型 责任链
		chain.init();
		User user = chain.doHandler(userLoginQO);
		//todo 保存登陆日志

		//装载用户信息
		vo.setUser(this.assembleUserBaseInfo(user));

		//装载用户权限信息
		CurrentUserAuthorityInfo currentUserAuthorityInfo =
				assembleUserAuthInfo(user, true,null, UserLoginDeviceTypeEnum.getEnumByKey(userLoginQO.getLoginDeviceType()));
		vo.setAuthority(currentUserAuthorityInfo);

		//装载组织机构
		CurrentUserOrgInfo currentUserOrgInfo = assembleUserOrgInfo(user);
		vo.setOrg(currentUserOrgInfo);

		//装载承包商
		CurrentUserContractorInfo contractorInfo=assembleUserContractorInfo(user);
		vo.setContractor(contractorInfo);

		//装载其他信息
		CurrentUserOtherInfo otherInfo=assembleUserOtherInfo(user);
		vo.setOther(otherInfo);


		return vo;
	}

	/**
	 * tokenVerify  验证token,并返回用户认证信息
	 *
	 * @param token
	 * @return priv.conceit.sc.hoxton.common.web.WebResponse
	 * @author conceit
	 * @date 2020/11/3 16:29
	 */
	@Override
	public CurrentUserInfoDTO tokenVerify(String token) {


		//获取完整Key
		String wholeToken = tokenUtil.generateWholeCacheToken(token);
		boolean flag = redisTemplate.hasKey(wholeToken);
		if (!flag) {
			throw new WebException(WebExceptionEnum.EX_USER_TOKEN_ERROR);
		}

		//解析token
		Map<String, String> map = tokenUtil.parseCacheToken(token, authProperties.getToken().getSeparator(), authProperties.getToken().getSecretKey());
		Long userId = Long.valueOf(map.get("userId"));
		UserLoginDeviceTypeEnum loginDeviceType = UserLoginDeviceTypeEnum.valueOf(map.get("userLoginDeviceTypeEnum"));

		//装载用户信息
		CurrentUserInfoDTO currentUserInfoDTO = new CurrentUserInfoDTO();
		User user = this.getById(userId);
		if (Objects.isNull(user)) {
			throw new WebException(WebExceptionEnum.EX_USER_ID_INVALID);
		}
		currentUserInfoDTO.setUser(this.assembleUserBaseInfo(user));

		//装载用户权限信息
		CurrentUserAuthorityInfo currentUserAuthorityInfo = assembleUserAuthInfo(user,  false, token,null);
		currentUserInfoDTO.setAuthority(currentUserAuthorityInfo);

		//装载其他信息 内部不需要

		//装载组织机构
		CurrentUserOrgInfo currentUserOrgInfo = assembleUserOrgInfo(user);
		currentUserInfoDTO.setOrg(currentUserOrgInfo);

		//装载承包商
		CurrentUserContractorInfo contractorInfo=assembleUserContractorInfo(user);
		currentUserInfoDTO.setContractor(contractorInfo);

		return currentUserInfoDTO;
	}

	/**
	 * assembleUserContractorInfo
	 * 装配承包商信息
	 *
	 * @param user
	 * @return priv.conceit.sc.hoxton.common.entity.auth.authority.dto.CurrentUserContractorInfo
	 * @author conceit
	 * @date 2020/11/25 18:38
	 */
	private CurrentUserContractorInfo assembleUserContractorInfo(User user) {
		CurrentUserContractorInfo info=new CurrentUserContractorInfo();

		//是否需要加载
		//用户非承包商用户 且在承包商系统中存在对应的承包商单位
		if (Objects.isNull(user.getType()) ||
				!UserTypeEnum.CONTRACTOR.getKey().equals(user.getType())) {
			return info;
		}
		List<Contractor>  contractors=new ArrayList<>();

		//从数据库中加载
		if(!authProperties.getContractor().getCache()){
			contractors=contractorService.load(Arrays.asList(user.getId()));
			info.setContractors(contractors);
			return info;
		}
		String key = redisExecuteUtil.buildProjectKey(CachePrefixKeyEnum.AUTH_CONTRACTORS, user.getId().toString());

		//从缓存加载
		contractors= (List<Contractor>) redisTemplate.opsForValue().get(key);
		if(CollUtil.isEmpty(contractors)){
			contractors=contractorService.load(Arrays.asList(user.getId()));
			redisTemplate
					.opsForValue()
					.set(key, contractors, authProperties.getContractor().getExpiresTime(), authProperties.getContractor().getExpiresTimeUnit());
		}

		info.setContractors(contractors);
		return info;
	}


	/**
	 * assembleUserOrgInfo 装载用户组织架构信息
	 * 用户非承包商用户且存在于组织架构内 才会进行组装
	 * 如果该用户有部门 没有公司 则只返回部门
	 * @param user
	 * @return priv.conceit.sc.hoxton.common.entity.auth.authority.dto.CurrentUserOrgInfo
	 * @author conceit
	 * @date 2020/11/25 15:48
	 */
	private CurrentUserOrgInfo assembleUserOrgInfo(User user) {

		CurrentUserOrgInfo info = new CurrentUserOrgInfo();

		assembleUserOrgDeptInfo(info,user);
		assembleUserOrgCompanyInfo(info,user);

		return info;
	}

	/**
	 * assembleUserOrgDeptInfo 装载部门信息
	 * 如果该用户有部门 没有公司 则只返回部门
	 *
	 * @param info
	 * @param user
	 * @return void
	 * @author conceit
	 * @date 2020/11/25 17:32
	 */
	private void assembleUserOrgCompanyInfo(CurrentUserOrgInfo info,User user){
		Company company=null;
		if(Objects.isNull(info.getDept())){
			return;
		}

		//是否存在公司
		Long companyId=info.getDept().getCompanyId();
		if(Objects.isNull(companyId)){
			return;
		}

		//不开启缓存
		if(!authProperties.getOrg().getCache()){
			company=this.companyService.getById(companyId);
			info.setCompany(company);
			return;
		}

		String companyKey = redisExecuteUtil.buildProjectKey(CachePrefixKeyEnum.AUTH_ORG_COMPANY, user.getId().toString());
		/**
		 * 加载缓存数据
		 */
		company = (Company) redisTemplate.opsForValue().get(companyKey);
		if (Objects.isNull(company)) {
			//加载db
			company = companyService.getById(user.getDeptId());
			if (Objects.nonNull(company)){
				redisTemplate.opsForValue().set(companyKey, company, authProperties.getOrg().getExpiresTime(), authProperties.getOrg().getExpiresTimeUnit());
			}
		}
		info.setCompany(company);
	}

	/**
	 * assembleUserOrgDeptInfo 装载部门信息
	 *
	 * @param info
  	 * @param user
	 * @return void
	 * @author conceit
	 * @date 2020/11/25 17:32
	 */
	private void assembleUserOrgDeptInfo(CurrentUserOrgInfo info,User user){
		/**
		 * 是否需要加载
		 */
		if (Objects.isNull(user.getType()) ||
				Objects.isNull(user.getDeptId()) ||
				!UserTypeEnum.OWN.getKey().equals(user.getType())) {
			return;
		}

		Dept dept = null;

		/**
		 * 不加载缓存
		 */
		if (!authProperties.getOrg().getCache()) {
			dept = deptService.getById(user.getDeptId());
			info.setDept(dept);
			return;
		}

		String deptKey = redisExecuteUtil.buildProjectKey(CachePrefixKeyEnum.AUTH_ORG_DEPT, user.getId().toString());

		/**
		 * 加载缓存数据
		 */
		dept = (Dept) redisTemplate.opsForValue().get(deptKey);
		if (Objects.nonNull(dept)) {
			//加载db
			dept = deptService.getById(user.getDeptId());
			if (Objects.nonNull(dept)){
				redisTemplate.opsForValue().set(deptKey, dept, authProperties.getOrg().getExpiresTime(), authProperties.getOrg().getExpiresTimeUnit());
			}
		}
		info.setDept(dept);
	}


	/**
	 * assembleUserBaseInfo 组装
	 *
	 * @param user
	 * @return priv.conceit.sc.hoxton.common.entity.auth.authority.dto.CurrentUserBaseInfo
	 * @author conceit
	 * @date 2020/11/25 14:27
	 */
	private CurrentUserBaseInfo assembleUserBaseInfo(User user) {
		CurrentUserBaseInfo info = new CurrentUserBaseInfo();
		BeanUtils.copyProperties(user, info);
		return info;
	}


	/**
	 * assembleUserAuthInfo 装载用户认证信息 用户 角色 权限 token
	 * 此步骤为核心步骤，可放置于缓存减少与数据库的交互
	 * 用户本身的数据不太建议放置于缓存
	 * ARK PLAN
	 *
	 * @param user            user
	 * @param isLogin         是否为登陆 是则会新增token
	 * @param token           用户传入的token 登陆不用传入
	 * @param loginDeviceType 用户登陆端 登陆不用传入
	 * @return priv.conceit.sc.hoxton.common.entity.auth.dto.CurrentUserBaseInfo
	 * @author conceit
	 * @date 2020/11/12 17:22
	 */
	@SuppressWarnings("all")
	public CurrentUserAuthorityInfo assembleUserAuthInfo(User user, Boolean isLogin, String token ,UserLoginDeviceTypeEnum loginDeviceType) {

		CurrentUserAuthorityInfo info = new CurrentUserAuthorityInfo();

		/**
		 * 是否是登陆？
		 */
		if (isLogin) {
			String newToken = tokenUtil.generateCacheToken(user.getId(), loginDeviceType, authProperties.getToken().getSeparator(), authProperties.getToken().getSecretKey());
			Boolean flag = tokenUtil.saveOrUpdateToken(newToken,authProperties.getToken().getExpiresTime(), authProperties.getToken().getExpiresTimeUnit());
			if (!flag) {
				throw new WebException(WebExceptionEnum.EX_USER_TOKEN_GENERATE_ERROR);
			}
			info.setToken(newToken);
		} else {
			info.setToken(token);
		}

		/**
		 * 装配 角色和资源
		 */
		this.assembleUserRoleInfo(info, user.getId());
		this.assembleUserResourceInfo(info,user.getId());

		return info;
	}


	/**
	 * assembleUserRoleInfo
	 * 角色和权限的数据全同步至缓存较好
	 * 如果直接和用户进行绑定在进行缓存，后续的维护工作不好处理
	 *
	 *
	 *
	 * @param info
	 * @param userId
	 * @return void
	 * @author conceit
	 * @date 2020/11/24 16:35
	 */
	private void assembleUserRoleInfo(CurrentUserAuthorityInfo info, Long userId) {


		List<Role> allRoles = new ArrayList<>();
		List<UserRole> allUserRoles = new ArrayList<>();
		List<Role> filteredRoles = new ArrayList<>();
		List<Long> roleIds = new ArrayList<>();


		//是否执行缓存
		if (!authProperties.getAuthority().getCache()) {
			allRoles = this.roleService.list();
			allUserRoles=this.userRoleService.list();
			roleIds=tokenUtil.matchRoleIds(allUserRoles,userId);
			filteredRoles=tokenUtil.matchRoles(allRoles,roleIds);
			info.setRoles(filteredRoles);
			return;
		}


		String roleKey = redisExecuteUtil.buildProjectKey(CachePrefixKeyEnum.AUTH_ROLE);
		String userRoleKey = redisExecuteUtil.buildProjectKey(CachePrefixKeyEnum.AUTH_USER_ROLE);

		//加载缓存
		allRoles = (List<Role>) redisTemplate.opsForValue().get(roleKey);
		allUserRoles = (List<UserRole>) redisTemplate.opsForValue().get(userRoleKey);

		//角色缓存存在？
		if (CollUtil.isEmpty(allRoles)) {
			allRoles = this.roleService.list();
			redisTemplate.opsForValue().set(roleKey, allRoles,
					authProperties.getAuthority().getExpiresTime(),
					authProperties.getAuthority().getExpiresTimeUnit());
		}
		//用户-角色关联数据是否存在？
		if(CollUtil.isEmpty(allUserRoles)){
			allUserRoles=this.userRoleService.list();
			redisTemplate.opsForValue().set(userRoleKey, allUserRoles,
					authProperties.getAuthority().getExpiresTime(),
					authProperties.getAuthority().getExpiresTimeUnit());
		}
		roleIds=tokenUtil.matchRoleIds(allUserRoles,userId);
		filteredRoles=tokenUtil.matchRoles(allRoles,roleIds);
		info.setRoles(filteredRoles);
	}


	/**
	 * assembleUserResourceInfo
	 *
	 * @param info
	 * @param userId
	 * @return void
	 * @author conceit
	 * @date 2020/11/24 18:16
	 */
	private void assembleUserResourceInfo(CurrentUserAuthorityInfo info, Long userId) {

		//如果该用户没有角色 则跳过配置权限
		if (CollUtil.isEmpty(info.getRoles())) {
			return;
		}

		List<Long> roleIds = info.getRoles().stream().map(Role::getId).collect(Collectors.toList());
		List<Resource> allResources = new ArrayList<>();
		List<RoleResource> allRoleResources = new ArrayList<>();
		List<Resource> filteredResources = new ArrayList<>();
		List<Long> resourceIds = new ArrayList<>();


		//是否执行缓存
		if (!authProperties.getAuthority().getCache()) {
			allResources=resourceService.list();
			allRoleResources=roleResourceService.list();
			resourceIds=tokenUtil.matchResourceIds(allRoleResources,roleIds);
			filteredResources=tokenUtil.matchResources(allResources,resourceIds);
			info.setResources(filteredResources);
			return;
		}

		//执行缓存
		String resourceKey = redisExecuteUtil.buildProjectKey(CachePrefixKeyEnum.AUTH_AUTHORITY);
		String roleResourceKey = redisExecuteUtil.buildProjectKey(CachePrefixKeyEnum.AUTH_ROLE_AUTHORITY);


		allResources = (List<Resource>) redisTemplate.opsForValue().get(resourceKey);
		allRoleResources = (List<RoleResource>) redisTemplate.opsForValue().get(roleResourceKey);


		if (CollUtil.isEmpty(allResources)) {
			allResources=resourceService.list();
			redisTemplate.opsForValue().set(resourceKey, allResources,
					authProperties.getAuthority().getExpiresTime(),
					authProperties.getAuthority().getExpiresTimeUnit());
		}

		if(CollUtil.isEmpty(allRoleResources)){
			allRoleResources=roleResourceService.list();
			redisTemplate.opsForValue().set(roleResourceKey, allRoleResources,
					authProperties.getAuthority().getExpiresTime(),
					authProperties.getAuthority().getExpiresTimeUnit());
		}

		resourceIds=tokenUtil.matchResourceIds(allRoleResources,roleIds);
		filteredResources=tokenUtil.matchResources(allResources,resourceIds);
		info.setResources(filteredResources);
	}


	/**
	 * assembleUserOtherInfo 装载用户其他信息
	 * 1-app版本更新
	 * 2-登陆推送
	 * 3-其他
	 *
	 * @param authUser
	 * @return priv.conceit.sc.hoxton.common.entity.auth.dto.CurrentUserBaseInfo
	 * @author conceit
	 * @date 2020/11/12 17:22
	 */
	public CurrentUserOtherInfo assembleUserOtherInfo(User authUser) {
		CurrentUserOtherInfo currentUserOtherInfo = new CurrentUserOtherInfo();
		return currentUserOtherInfo;
	}
}
